package syncutl

import (
	"fmt"
	"gitee.com/xiakai001/goutl/errutl"
	"math"
	"runtime/debug"
	"sync"
)

func Run(tasks ...func()) {

	if len(tasks) == 0 {
		return
	}

	if len(tasks) == 1 {
		tasks[0]()
		return
	}

	var wg sync.WaitGroup
	wg.Add(len(tasks))

	errors := errutl.NewErrs()
	for _, t := range tasks {
		t := t
		go func() {
			defer func() {
				wg.Done()
				if e := recover(); e != nil {
					errors.Append(fmt.Errorf("[catch panic] e=%v\n%s", e, debug.Stack()))
				}
			}()
			t()
		}()
	}
	wg.Wait()

	if errors.IsNotNil() {
		panic(errors.Error())
	}
}

func ChunkWithErr[T any](length, maxConcurrent, chunkSize int,
	producer func(start, end int) (resp T, err error),
	consumer func(start, end int, resp T, err error)) {

	if maxConcurrent <= 0 || chunkSize <= 0 {
		panic("maxConcurrent <= 0 or chunkSize <= 0")
	}

	index := 0
	chunkIndex := 0
	lengthTask := int(math.Ceil(float64(length) / float64(chunkSize)))
	var lock sync.Mutex
	var wg sync.WaitGroup
	wg.Add(lengthTask)
	throttleChan := make(chan struct{}, maxConcurrent)

	panicErrs := errutl.NewErrs()
	for {
		start := index
		end := index + chunkSize
		if end > length {
			end = length
		}
		throttleChan <- struct{}{}
		go func(chunkIndex int) {
			defer func() {
				wg.Done()
				<-throttleChan
				if e := recover(); e != nil {
					panicErrs.Append(fmt.Errorf("[catch panic] e=%v\n%s", e, debug.Stack()))
				}
			}()

			response, err := producer(start, end)
			if consumer != nil {
				lock.Lock()
				defer lock.Unlock()
				consumer(start, end, response, err)
			}
		}(chunkIndex)
		chunkIndex++
		index = index + chunkSize
		if index >= length {
			break
		}
	}
	wg.Wait()
	close(throttleChan)

	if panicErrs.IsNotNil() {
		panic(panicErrs.Error())
	}
}

func Chunk[T any](length, maxConcurrent, chunkSize int,
	producer func(start, end int) (resp T, err error),
	consumer func(start, end int, resp T)) error {

	errors := errutl.NewErrs()
	ChunkWithErr(length, maxConcurrent, chunkSize, producer, func(start, end int, resp T, err error) {
		if err != nil {
			errors.Append(err)
		}
		consumer(start, end, resp)
	})

	if errors.IsNil() {
		return nil
	}

	return errors
}

func ChunkArr[K, V any](keys []K, maxConcurrent, chunkSize int,
	producer func(keys []K) (resArr []V, err error)) ([]V, error) {

	if len(keys) == 0 {
		return []V{}, nil
	}

	resArr := make([]V, 0, len(keys))
	err := Chunk(len(keys), maxConcurrent, chunkSize, func(start, end int) ([]V, error) {
		return producer(keys[start:end])
	}, func(start, end int, resp []V) {
		resArr = append(resArr, resp...)
	})
	if err != nil {
		return nil, errutl.Wrap(err)
	}

	return resArr, nil
}

func ChunkOrderArr[K, V any](keys []K, maxConcurrent, chunkSize int,
	producer func(keys []K) (resArr []V, err error)) ([]V, error) {

	if len(keys) == 0 {
		return []V{}, nil
	}

	resArr := make([]V, len(keys))
	err := Chunk(len(keys), maxConcurrent, chunkSize, func(start, end int) ([]V, error) {
		return producer(keys[start:end])
	}, func(start, end int, resp []V) {
		if len(resp) != end-start {
			panic(fmt.Sprintf("len(resp):%d != end-start:%d", len(resp), end-start))
		}
		for i, v := range resp {
			resArr[start+i] = v
		}
	})
	if err != nil {
		return nil, errutl.Wrap(err)
	}

	return resArr, nil
}

func ChunkMap[K comparable, V any](keys []K, maxConcurrent, chunkSize int,
	producer func(keys []K) (resArr map[K]V, err error)) (map[K]V, error) {

	if len(keys) == 0 {
		return map[K]V{}, nil
	}

	resMap := make(map[K]V, len(keys))
	err := Chunk(len(keys), maxConcurrent, chunkSize, func(start, end int) (map[K]V, error) {
		return producer(keys[start:end])
	}, func(start, end int, resp map[K]V) {
		for k, v := range resp {
			resMap[k] = v
		}
	})
	if err != nil {
		return nil, errutl.Wrap(err)
	}

	return resMap, nil
}

func ChunkArrMap[E any, K comparable, V any](keys []E, maxConcurrent, chunkSize int,
	keyFn func(V) (K, bool),
	producer func(keys []E) (resArr []V, err error)) (map[K]V, error) {

	if len(keys) == 0 {
		return map[K]V{}, nil
	}

	resMap := make(map[K]V, len(keys))
	err := Chunk(len(keys), maxConcurrent, chunkSize, func(start, end int) ([]V, error) {
		return producer(keys[start:end])
	}, func(start, end int, resp []V) {
		for _, v := range resp {
			k, b := keyFn(v)
			if b {
				resMap[k] = v
			}
		}
	})

	if err != nil {
		return nil, errutl.Wrap(err)
	}

	return resMap, nil
}
